第12章 数据降维之PCA¶
建立模型分析特征数据时, 很可能会面临特征数据维度过大的问题
例如, 根据已有的信用卡持有人信息及其违约数据来建立信用卡违约预测模型时, 数据可能包含申请人的收入、年龄、性别、婚姻状况等数百个维度的特征, 如果将所有特征数据都用来拟合模型, 会提高模型的复杂度, 造成过拟合风险显著增大, 且不同的特征数据间可能存在共线性, 此时就需要对数据进行降维, 以浓缩特征向量
这章讲解数据降维的常见算法PCA (Principal Component Analysis, 主成分分析)的原理和代码实现以及一个人脸识别模型的实现
12.1 数据降维¶
如果特征变量的数量非常多, 在训练模型之前, 往往需要进行数据降维
降维的方法主要有选择特征和抽取特征两种:
选择特征是从原有的特征中挑选出最佳的特征
抽取特征则是将数据由高维向低维投影, 进行坐标的线性转换
PCA即为典型的抽取特征的方法, 它不仅是对高维数据进行降维, 更重要的是经过降维去除噪声, 发现数据中的模式
12.1.1 PCA的基本原理¶
二维空间降维¶
假设在二维坐标系中有一组数据, 分别是A(1,1), B(2,2), C(3,3), 降维的目的就是把这组二维数据转换为一维数据
明显可以看到, 这组数据在 $y = x$ 这条直线上, 如果需要将这组数据从二维降至一维, 可以将直线 $y = x$ 作为新的坐标轴, 在新的坐标系中只有一条横轴 $x'$, 没有纵轴, 这样就把原来的二维数据转换为一维数据了
其实这里的数据降维就是对原来的二维数据进行了简单的线性组合
在实际进行数据降维前, 需要先对特征数据做零均值化处理, 即将每个特征维度减去该特征的均值
每个特征都减去均值后, 再对零均值化后的数据进行线性组合
总结来说, 二维到一维的数据降维的本质就是将原始数据做零均值化处理后, 寻找合适的线性组合, 将二维数据转换为一维数据
n维数据降维¶
如果原特征变量有 $n$ 个, 那么就是 $n$ 维空间降维, $n$ 维空间降维的思路和二维空间降维的思路是一致的, 都是寻找合适的线性组合系数
例如, 将 $n$ 维数据 $(X_1, X_2, \cdots, X_n)$ 转换为一维数据, 就是寻找如下所示的线性组合系数 $(a_1, a_2, \cdots, a_n)$
$$ F_1 = a_1X_1 + a_2X_2 + \cdots + a_nX_n $$
将 $n$ 维数据 $(X_1, X_2, \cdots, X_n)$ 转换成 $k$ 维数据 $(F_1, F_2, \cdots, F_k)$ 本质也是一样的, 只是从构造1个线性组合变为构造 $k$ 个线性组合
$$ \begin{aligned} F_1 &= a_{11}X_1 + a_{12}X_2 + \cdots + a_{1n}X_n\\ F_2 &= a_{21}X_1 + a_{22}X_2 + \cdots + a_{2n}X_n\\ \cdots\\ F_k &= a_{k1}X_1 + a_{k2}X_2 + \cdots + a_{kn}X_n\\ \end{aligned} $$
即
$$ F_{m \times k} = X_{m \times n} A_{n \times k} $$
其中 $m$ 为样本数, $A_{n \times k} = (\overrightarrow{a}_1, \overrightarrow{a}_2, \cdots, \overrightarrow{a}_k)$, $\overrightarrow{a}_i$ 为 $n$ 维列向量
其中要满足的线性代数条件如下
每个主成分的系数平方和为1
$$ ||\overrightarrow{a}_i||_2^2 = 1 $$
($||x||_p$ 是向量 $x$ 的 p-范数, $||x||_p = (|x_1|^p + |x_2|^p + \cdots + |x_n|^p)^{\frac{1}{p}}$)
各个主成分互不相关
$$ Cov(F_i, F_j) = 0, \forall i \neq j $$
主成分的方差依次递减, 重要性依次递减
$$ Var(F_1) \geqslant Var(F_2) \geqslant \cdots \geqslant Var(F_k) $$
线性推导过程:
将原始 $n$ 维特征数据按行做零均值化处理后求得 $n$ 维协方差矩阵 ($n \times n$), 计算协方差矩阵的特征值及其对应的单位特征向量
将特征向量按特征值从大到小排序, 使用前 $k$ 行组成的矩阵乘以原始 $n$ 维数据矩阵, 即可得到降维后的 $k$ 维数据矩阵
在通过该矩阵就能获得线性组合系数, 这些线性组合系数能最大化样本方差, 使新的 $k$ 个特征互不相关
实际应用中, 只需要明白核心逻辑即可, 不需要深究其数学原理
12.1.2 PCA的代码实现¶
二维空间降维的代码实现¶
import numpy as np
X = np.array([[1, 1], [2, 2], [3, 3]])
X
array([[1, 1], [2, 2], [3, 3]])
# 也可以通过pandas库来构造数据,效果一样
import pandas as pd
X = pd.DataFrame([[1, 1], [2, 2], [3, 3]])
X
0 | 1 | |
---|---|---|
0 | 1 | 1 |
1 | 2 | 2 |
2 | 3 | 3 |
# 数据降维,由二维降至一维
from sklearn.decomposition import PCA
pca = PCA(n_components=1)
pca.fit(X) # 进行降维模型训练
X_transformed = pca.transform(X) # 进行数据降维,并赋值给X_transformed
X_transformed # 查看降维后的结果
array([[-1.41421356], [ 0. ], [ 1.41421356]])
# 查看此时的维度
X_transformed.shape
(3, 1)
# 查看降维的系数
pca.components_
array([[0.70710678, 0.70710678]])
# 查看线性组合表达式
a = pca.components_[0][0]
b = pca.components_[0][1]
print(f'{a:.3f} * X + {b:.3f} * Y')
0.707 * X + 0.707 * Y
三维空间的代码实现¶
import pandas as pd
X = pd.DataFrame([[45, 0.8, 9120], [40, 0.12, 2600], [38, 0.09, 3042], [30, 0.04, 3300], [39, 0.21, 3500]], columns=['年龄(岁)', '负债比率', '月收入(元)'])
X
年龄(岁) | 负债比率 | 月收入(元) | |
---|---|---|---|
0 | 45 | 0.80 | 9120 |
1 | 40 | 0.12 | 2600 |
2 | 38 | 0.09 | 3042 |
3 | 30 | 0.04 | 3300 |
4 | 39 | 0.21 | 3500 |
# 因为三个指标数据的量级相差较大,所以可以先进行数据归一化处理
from sklearn.preprocessing import StandardScaler
X_new = StandardScaler().fit_transform(X)
X_new # 查看归一化后的数据
array([[ 1.36321743, 1.96044639, 1.98450514], [ 0.33047695, -0.47222431, -0.70685302], [-0.08261924, -0.57954802, -0.52440206], [-1.73500401, -0.75842087, -0.41790353], [ 0.12392886, -0.15025319, -0.33534653]])
from sklearn.decomposition import PCA
pca = PCA(n_components=2)
pca.fit(X_new) # 进行降维模型训练
X_transformed = pca.transform(X_new) # 进行数据降维,并赋值给X_transformed
X_transformed # 查看降维后的结果
array([[ 3.08724247, -0.32991205], [-0.52888635, 0.74272137], [-0.70651782, 0.33057258], [-1.62877292, -1.05218639], [-0.22306538, 0.30880449]])
# 查看降维的系数
pca.components_
array([[ 0.52952108, 0.61328179, 0.58608264], [ 0.82760701, -0.22182579, -0.51561609]])
这里就是 $ X\_transformed_{m \times 2} = X\_new_{m \times 3} \times PCA.components^T_{3 \times 2}$
对于 $X\_transformed$ 中的每一行, 第 $i$ 维的特征值等于 $X\_new$ 中对应行与 $PCA.components$ 中第 $i$ 行的内积
dim = ['年龄(岁)', '负债比率', '月收入(元)']
for i in pca.components_:
formula = []
for j in range(len(i)):
formula.append(f'{i[j]:.3f} * {dim[j]}')
print(" + ".join(formula))
0.530 * 年龄(岁) + 0.613 * 负债比率 + 0.586 * 月收入(元) 0.828 * 年龄(岁) + -0.222 * 负债比率 + -0.516 * 月收入(元)
# 如果不想显示具体的特征名称,可以采用如下的写法
dim = ['X', 'Y', 'Z']
for i in pca.components_:
formula = []
for j in range(len(i)):
formula.append(f'{i[j]:.3f} * {dim[j]}')
print(" + ".join(formula))
0.530 * X + 0.613 * Y + 0.586 * Z 0.828 * X + -0.222 * Y + -0.516 * Z
12.2 案例实战: 人脸识别模型¶
12.2.1 案例背景¶
人脸识别是基于人的脸部特征信息进行身份识别的一种生物识别技术。该技术蓬勃发展,应用广泛,如人脸识别门禁系统、刷脸支付软件等
人脸识别在本质上是根据每张人脸图像中不同像素点的颜色进行数据建模与判断。人脸图像的每个像素点的颜色都有不同的值,这些值可以组成人脸的特征向量,不过因为人脸图像的像素点很多,所以特征变量也很多,需要利用PCA进行数据降维
12.2.2 脸数据读取、处理与变量提取¶
1.读取人脸照片数据¶
数据集中图片的文件名由4部分组成:
第1部分是该张图片对应的人脸编号
第2部分是固定分隔符 “_”
第3部分是该张图片在该人脸10张图片中的顺序编号
第4部分是文件扩展名“.jpg”
# 在python中获取文件名列表
import os
names = os.listdir('olivettifaces') # listdir()函数返回指定文件夹下的文件或文件夹的名称列表
names[0:5] # 查看前5项读取的文件名
['10_0.jpg', '10_1.jpg', '10_2.jpg', '10_3.jpg', '10_4.jpg']
# 获取到文件名称后,便可以通过如下代码在Python中查看这些图片
from PIL import Image
img0 = Image.open('olivettifaces\\' + names[0])
# img0.show()
img0 # 在Jupyter Notebook中可以直接输入变量名查看图像
2.人脸数据处理:特征变量提取¶
# 图像灰度处理及数值化处理
import numpy as np
img0 = img0.convert('L')
img0 = img0.resize((32, 32))
arr = np.array(img0)
arr # 查看数值化后的结果
array([[186, 76, 73, ..., 100, 103, 106], [196, 85, 68, ..., 85, 106, 103], [193, 69, 79, ..., 82, 99, 100], ..., [196, 87, 193, ..., 103, 66, 52], [219, 179, 202, ..., 150, 127, 109], [244, 228, 230, ..., 198, 202, 206]], dtype=uint8)
# 如果觉得numpy格式的arr不好观察,则可以通过pandas库将其转为DataFrame格式进行观察
import pandas as pd
pd.DataFrame(arr)
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | ... | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 186 | 76 | 73 | 87 | 89 | 88 | 75 | 81 | 100 | 102 | ... | 71 | 75 | 75 | 73 | 76 | 85 | 95 | 100 | 103 | 106 |
1 | 196 | 85 | 68 | 78 | 104 | 97 | 100 | 94 | 83 | 87 | ... | 52 | 59 | 70 | 85 | 62 | 82 | 89 | 85 | 106 | 103 |
2 | 193 | 69 | 79 | 92 | 105 | 102 | 112 | 117 | 106 | 94 | ... | 41 | 45 | 50 | 76 | 59 | 74 | 83 | 82 | 99 | 100 |
3 | 186 | 67 | 71 | 75 | 85 | 99 | 114 | 115 | 109 | 109 | ... | 42 | 43 | 40 | 52 | 41 | 61 | 69 | 76 | 76 | 108 |
4 | 179 | 46 | 41 | 50 | 53 | 69 | 80 | 91 | 108 | 104 | ... | 43 | 37 | 30 | 31 | 35 | 43 | 59 | 61 | 56 | 101 |
5 | 173 | 33 | 43 | 49 | 48 | 53 | 64 | 69 | 72 | 75 | ... | 38 | 36 | 33 | 32 | 39 | 45 | 68 | 60 | 45 | 83 |
6 | 173 | 30 | 37 | 41 | 42 | 57 | 81 | 88 | 77 | 64 | ... | 31 | 32 | 35 | 32 | 35 | 49 | 65 | 64 | 53 | 87 |
7 | 171 | 24 | 32 | 36 | 42 | 55 | 77 | 101 | 107 | 102 | ... | 54 | 64 | 63 | 51 | 53 | 60 | 56 | 46 | 49 | 89 |
8 | 170 | 21 | 31 | 29 | 28 | 35 | 47 | 62 | 76 | 83 | ... | 105 | 101 | 89 | 63 | 45 | 42 | 41 | 37 | 61 | 101 |
9 | 172 | 21 | 22 | 27 | 28 | 30 | 33 | 43 | 46 | 44 | ... | 129 | 118 | 103 | 74 | 39 | 27 | 36 | 34 | 68 | 101 |
10 | 171 | 23 | 30 | 21 | 30 | 36 | 44 | 51 | 46 | 41 | ... | 136 | 126 | 120 | 102 | 70 | 40 | 30 | 35 | 78 | 101 |
11 | 175 | 21 | 33 | 31 | 42 | 54 | 71 | 73 | 64 | 59 | ... | 142 | 133 | 122 | 114 | 111 | 77 | 31 | 36 | 95 | 104 |
12 | 192 | 42 | 27 | 37 | 63 | 87 | 99 | 111 | 116 | 116 | ... | 105 | 108 | 108 | 107 | 109 | 104 | 58 | 59 | 100 | 97 |
13 | 196 | 82 | 41 | 58 | 102 | 112 | 110 | 110 | 107 | 108 | ... | 70 | 71 | 86 | 102 | 107 | 116 | 84 | 77 | 95 | 99 |
14 | 192 | 88 | 78 | 95 | 96 | 87 | 81 | 72 | 51 | 40 | ... | 62 | 73 | 73 | 101 | 116 | 116 | 99 | 80 | 88 | 105 |
15 | 190 | 88 | 102 | 114 | 99 | 76 | 55 | 55 | 50 | 37 | ... | 100 | 112 | 120 | 120 | 125 | 125 | 105 | 97 | 101 | 98 |
16 | 189 | 106 | 111 | 113 | 137 | 124 | 113 | 109 | 103 | 96 | ... | 134 | 137 | 142 | 146 | 134 | 120 | 96 | 115 | 122 | 93 |
17 | 188 | 106 | 132 | 119 | 142 | 160 | 158 | 153 | 148 | 145 | ... | 163 | 158 | 156 | 142 | 124 | 110 | 100 | 114 | 108 | 86 |
18 | 193 | 83 | 140 | 130 | 122 | 141 | 153 | 160 | 168 | 177 | ... | 163 | 158 | 148 | 129 | 112 | 103 | 99 | 99 | 78 | 77 |
19 | 190 | 81 | 117 | 127 | 107 | 120 | 134 | 146 | 163 | 166 | ... | 157 | 145 | 132 | 117 | 105 | 103 | 90 | 64 | 67 | 72 |
20 | 193 | 83 | 84 | 106 | 104 | 113 | 122 | 134 | 138 | 143 | ... | 142 | 129 | 116 | 109 | 105 | 102 | 86 | 55 | 60 | 63 |
21 | 194 | 78 | 87 | 91 | 92 | 108 | 113 | 122 | 127 | 140 | ... | 140 | 128 | 118 | 113 | 109 | 101 | 68 | 56 | 56 | 56 |
22 | 191 | 80 | 89 | 88 | 90 | 104 | 114 | 120 | 131 | 141 | ... | 130 | 129 | 119 | 108 | 103 | 101 | 50 | 53 | 55 | 53 |
23 | 189 | 77 | 89 | 91 | 86 | 93 | 111 | 122 | 133 | 129 | ... | 102 | 113 | 111 | 107 | 101 | 85 | 53 | 51 | 54 | 55 |
24 | 190 | 86 | 88 | 87 | 87 | 87 | 104 | 115 | 127 | 115 | ... | 105 | 108 | 104 | 102 | 97 | 55 | 53 | 50 | 53 | 59 |
25 | 187 | 74 | 89 | 81 | 93 | 130 | 103 | 96 | 110 | 108 | ... | 111 | 105 | 103 | 102 | 83 | 50 | 49 | 56 | 51 | 49 |
26 | 190 | 79 | 81 | 107 | 166 | 206 | 119 | 88 | 94 | 105 | ... | 102 | 104 | 99 | 100 | 111 | 111 | 57 | 48 | 52 | 53 |
27 | 192 | 78 | 83 | 173 | 211 | 158 | 114 | 100 | 87 | 94 | ... | 101 | 98 | 98 | 96 | 116 | 123 | 119 | 52 | 49 | 55 |
28 | 188 | 70 | 136 | 177 | 198 | 108 | 101 | 119 | 86 | 81 | ... | 98 | 98 | 93 | 82 | 80 | 123 | 145 | 73 | 43 | 51 |
29 | 196 | 87 | 193 | 187 | 179 | 113 | 123 | 123 | 110 | 81 | ... | 95 | 90 | 96 | 77 | 53 | 160 | 124 | 103 | 66 | 52 |
30 | 219 | 179 | 202 | 196 | 198 | 146 | 122 | 118 | 119 | 94 | ... | 92 | 90 | 87 | 57 | 89 | 126 | 140 | 150 | 127 | 109 |
31 | 244 | 228 | 230 | 231 | 233 | 213 | 188 | 195 | 193 | 189 | ... | 179 | 184 | 177 | 161 | 202 | 182 | 207 | 198 | 202 | 206 |
32 rows × 32 columns
# 上面获得的32*32的二维数组,还不利于数据建模,所以我们还需要通过reshape(1, -1)方法将其转换成一行(若reshape(-1,1)则转为一列),也即1*1024格式
arr = arr.reshape(1, -1)
print(arr) # 查看转换后的结果,这一行数就是代表那张人脸图片了,其共有32*32=1024列数
[[186 76 73 ... 198 202 206]]
因为总共有400张照片需要处理,若将400个二维数组堆叠起来会形成三维数组,因为我们需要使用flatten()函数将1*1024的二维数组降维成一维数组,并通过tolist()函数将其转为列表方便之后和其他图片的颜色数值信息一起处理
print(arr.flatten().tolist()) # 下面这一行数就是那张人脸转换后的结果了
[186, 76, 73, 87, 89, 88, 75, 81, 100, 102, 105, 92, 74, 65, 65, 53, 43, 55, 53, 42, 58, 77, 71, 75, 75, 73, 76, 85, 95, 100, 103, 106, 196, 85, 68, 78, 104, 97, 100, 94, 83, 87, 88, 89, 86, 70, 65, 61, 55, 52, 38, 32, 52, 66, 52, 59, 70, 85, 62, 82, 89, 85, 106, 103, 193, 69, 79, 92, 105, 102, 112, 117, 106, 94, 91, 112, 101, 87, 75, 61, 58, 54, 49, 48, 44, 41, 41, 45, 50, 76, 59, 74, 83, 82, 99, 100, 186, 67, 71, 75, 85, 99, 114, 115, 109, 109, 98, 101, 86, 68, 74, 65, 58, 53, 51, 52, 42, 40, 42, 43, 40, 52, 41, 61, 69, 76, 76, 108, 179, 46, 41, 50, 53, 69, 80, 91, 108, 104, 98, 93, 91, 88, 73, 60, 56, 55, 51, 49, 53, 55, 43, 37, 30, 31, 35, 43, 59, 61, 56, 101, 173, 33, 43, 49, 48, 53, 64, 69, 72, 75, 82, 84, 84, 82, 72, 75, 69, 71, 67, 56, 58, 55, 38, 36, 33, 32, 39, 45, 68, 60, 45, 83, 173, 30, 37, 41, 42, 57, 81, 88, 77, 64, 63, 64, 65, 64, 48, 48, 68, 62, 47, 50, 45, 31, 31, 32, 35, 32, 35, 49, 65, 64, 53, 87, 171, 24, 32, 36, 42, 55, 77, 101, 107, 102, 98, 83, 71, 64, 44, 48, 54, 64, 76, 65, 49, 48, 54, 64, 63, 51, 53, 60, 56, 46, 49, 89, 170, 21, 31, 29, 28, 35, 47, 62, 76, 83, 87, 78, 53, 58, 65, 83, 90, 97, 108, 101, 97, 105, 105, 101, 89, 63, 45, 42, 41, 37, 61, 101, 172, 21, 22, 27, 28, 30, 33, 43, 46, 44, 43, 46, 50, 63, 76, 87, 95, 104, 114, 120, 122, 125, 129, 118, 103, 74, 39, 27, 36, 34, 68, 101, 171, 23, 30, 21, 30, 36, 44, 51, 46, 41, 45, 52, 61, 70, 84, 101, 112, 119, 126, 131, 134, 138, 136, 126, 120, 102, 70, 40, 30, 35, 78, 101, 175, 21, 33, 31, 42, 54, 71, 73, 64, 59, 65, 78, 95, 106, 110, 116, 122, 130, 140, 143, 141, 142, 142, 133, 122, 114, 111, 77, 31, 36, 95, 104, 192, 42, 27, 37, 63, 87, 99, 111, 116, 116, 117, 117, 121, 122, 120, 119, 120, 121, 120, 113, 107, 106, 105, 108, 108, 107, 109, 104, 58, 59, 100, 97, 196, 82, 41, 58, 102, 112, 110, 110, 107, 108, 107, 107, 115, 126, 126, 118, 98, 87, 69, 57, 55, 55, 70, 71, 86, 102, 107, 116, 84, 77, 95, 99, 192, 88, 78, 95, 96, 87, 81, 72, 51, 40, 52, 53, 68, 89, 116, 111, 83, 61, 44, 55, 61, 42, 62, 73, 73, 101, 116, 116, 99, 80, 88, 105, 190, 88, 102, 114, 99, 76, 55, 55, 50, 37, 60, 53, 49, 84, 161, 155, 109, 94, 83, 76, 84, 88, 100, 112, 120, 120, 125, 125, 105, 97, 101, 98, 189, 106, 111, 113, 137, 124, 113, 109, 103, 96, 84, 84, 99, 123, 172, 176, 139, 130, 127, 120, 116, 115, 134, 137, 142, 146, 134, 120, 96, 115, 122, 93, 188, 106, 132, 119, 142, 160, 158, 153, 148, 145, 156, 151, 139, 139, 160, 177, 151, 138, 141, 153, 162, 164, 163, 158, 156, 142, 124, 110, 100, 114, 108, 86, 193, 83, 140, 130, 122, 141, 153, 160, 168, 177, 171, 154, 135, 137, 161, 176, 158, 146, 143, 142, 159, 164, 163, 158, 148, 129, 112, 103, 99, 99, 78, 77, 190, 81, 117, 127, 107, 120, 134, 146, 163, 166, 155, 134, 138, 137, 153, 164, 141, 132, 132, 127, 148, 156, 157, 145, 132, 117, 105, 103, 90, 64, 67, 72, 193, 83, 84, 106, 104, 113, 122, 134, 138, 143, 141, 138, 93, 69, 84, 86, 71, 62, 75, 106, 142, 146, 142, 129, 116, 109, 105, 102, 86, 55, 60, 63, 194, 78, 87, 91, 92, 108, 113, 122, 127, 140, 148, 154, 121, 80, 72, 66, 80, 96, 106, 112, 137, 147, 140, 128, 118, 113, 109, 101, 68, 56, 56, 56, 191, 80, 89, 88, 90, 104, 114, 120, 131, 141, 145, 150, 153, 142, 144, 124, 105, 111, 119, 121, 128, 128, 130, 129, 119, 108, 103, 101, 50, 53, 55, 53, 189, 77, 89, 91, 86, 93, 111, 122, 133, 129, 125, 126, 118, 117, 115, 111, 100, 85, 87, 86, 76, 88, 102, 113, 111, 107, 101, 85, 53, 51, 54, 55, 190, 86, 88, 87, 87, 87, 104, 115, 127, 115, 101, 88, 83, 84, 91, 96, 108, 114, 101, 98, 106, 103, 105, 108, 104, 102, 97, 55, 53, 50, 53, 59, 187, 74, 89, 81, 93, 130, 103, 96, 110, 108, 108, 129, 126, 112, 119, 108, 96, 98, 105, 110, 117, 118, 111, 105, 103, 102, 83, 50, 49, 56, 51, 49, 190, 79, 81, 107, 166, 206, 119, 88, 94, 105, 112, 116, 113, 106, 96, 92, 95, 102, 103, 107, 113, 108, 102, 104, 99, 100, 111, 111, 57, 48, 52, 53, 192, 78, 83, 173, 211, 158, 114, 100, 87, 94, 108, 114, 115, 119, 129, 146, 151, 144, 140, 133, 121, 112, 101, 98, 98, 96, 116, 123, 119, 52, 49, 55, 188, 70, 136, 177, 198, 108, 101, 119, 86, 81, 94, 105, 115, 123, 127, 128, 126, 122, 119, 109, 97, 97, 98, 98, 93, 82, 80, 123, 145, 73, 43, 51, 196, 87, 193, 187, 179, 113, 123, 123, 110, 81, 73, 81, 88, 96, 97, 95, 91, 91, 89, 86, 91, 99, 95, 90, 96, 77, 53, 160, 124, 103, 66, 52, 219, 179, 202, 196, 198, 146, 122, 118, 119, 94, 76, 73, 72, 74, 77, 73, 74, 79, 77, 83, 92, 94, 92, 90, 87, 57, 89, 126, 140, 150, 127, 109, 244, 228, 230, 231, 233, 213, 188, 195, 193, 189, 181, 173, 171, 171, 171, 168, 168, 171, 173, 178, 182, 179, 179, 184, 177, 161, 202, 182, 207, 198, 202, 206]
# 构造所有图片的特征变量
X = [] # 特征变量
for i in names:
img = Image.open('olivettifaces\\' + i)
img = img.convert('L')
img = img.resize((32, 32))
arr = np.array(img)
X.append(arr.reshape(1, -1).flatten().tolist())
import pandas as pd
X = pd.DataFrame(X)
X.head(5) # 查看400张图片转换后的结果
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | ... | 1014 | 1015 | 1016 | 1017 | 1018 | 1019 | 1020 | 1021 | 1022 | 1023 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 186 | 76 | 73 | 87 | 89 | 88 | 75 | 81 | 100 | 102 | ... | 179 | 184 | 177 | 161 | 202 | 182 | 207 | 198 | 202 | 206 |
1 | 196 | 90 | 97 | 98 | 98 | 87 | 101 | 89 | 65 | 73 | ... | 181 | 167 | 190 | 188 | 203 | 209 | 205 | 198 | 190 | 190 |
2 | 193 | 89 | 97 | 99 | 75 | 74 | 83 | 64 | 77 | 86 | ... | 178 | 178 | 156 | 185 | 195 | 201 | 206 | 201 | 189 | 190 |
3 | 192 | 84 | 93 | 89 | 97 | 89 | 66 | 60 | 60 | 57 | ... | 173 | 151 | 199 | 189 | 203 | 200 | 196 | 186 | 182 | 184 |
4 | 194 | 72 | 49 | 45 | 56 | 37 | 44 | 62 | 71 | 71 | ... | 192 | 194 | 192 | 176 | 174 | 224 | 200 | 218 | 176 | 168 |
5 rows × 1024 columns
print(X.shape) # 查看此时的表格结构
(400, 1024)
3.人脸数据处理:目标变量提取¶
# 获取目标变量y:第一张图片演示
print(int(names[0].split('_')[0]))
10
# 批量获取所有图片的目标变量y
y = [] # 目标变量
for i in names:
img = Image.open('olivettifaces\\' + i)
y.append(int(i.split('_')[0]))
print(y) # 查看目标变量,也就是对应的人员编号
[10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9]
12.2.3 据划分与降维¶
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=1)
# 数据降维模型训练
from sklearn.decomposition import PCA
pca = PCA(n_components=100)
pca.fit(X_train)
PCA(n_components=100)In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
PCA(n_components=100)
# 对训练集和测试集进行数据降维
X_train_pca = pca.transform(X_train)
X_test_pca = pca.transform(X_test)
# 我们通过如下代码验证PCA是否降维:
print(X_train_pca.shape)
print(X_test_pca.shape)
(320, 100) (80, 100)
# 如果想查看此时降维后的X_train_pca和X_test_pca,可以直接将它们打印出来查看,也可以将它们转为DataFrame格式进行查看,代码如下:
pd.DataFrame(X_train_pca).head()
# pd.DataFrame(X_test_pca).head()
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | ... | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | -843.848468 | -117.990083 | 341.041562 | -45.240791 | -265.560411 | 243.732769 | 280.603779 | -259.737068 | -216.102415 | -192.380205 | ... | 34.318728 | 2.792837 | -35.842708 | -6.309040 | -11.577465 | 7.730109 | 40.761569 | -36.876915 | 5.904517 | 21.997346 |
1 | 495.124726 | -937.701751 | -305.851409 | 4.351306 | -127.743015 | 504.793986 | 389.144513 | 5.118524 | 32.353108 | -103.516948 | ... | 29.356063 | -75.233485 | 18.607723 | -49.819543 | -10.210716 | 17.714219 | 15.776035 | -45.848006 | -18.048762 | -26.388540 |
2 | -201.200905 | 623.575255 | 130.691212 | 22.627082 | -427.373676 | -85.786784 | 531.627875 | 95.691713 | 176.386343 | 70.070456 | ... | 25.791654 | -34.335561 | 2.037512 | 24.364232 | -75.749108 | 66.600540 | -19.601073 | -75.682701 | 24.363958 | 22.369335 |
3 | -603.867640 | -744.880158 | -626.408564 | -598.649870 | -400.044101 | 7.220209 | -246.688151 | 58.416884 | 417.199237 | 121.976573 | ... | -35.618443 | 12.447379 | -21.560417 | 18.310737 | 9.675979 | 72.568819 | 21.155308 | 26.964040 | 0.463389 | -58.788670 |
4 | -935.882937 | 132.602933 | 441.577563 | 145.548390 | -260.280190 | 248.876747 | 235.116994 | -333.232315 | -98.692085 | -75.838789 | ... | 9.744077 | -30.824290 | -76.967401 | 8.543038 | 79.753247 | 3.739868 | 15.566506 | 40.380661 | -90.864002 | 92.131989 |
5 rows × 100 columns
12.2.4 模型的搭建与使用¶
from sklearn.neighbors import KNeighborsClassifier
knn = KNeighborsClassifier() # 建立KNN模型
knn.fit(X_train_pca, y_train) # 用降维后的训练集进行训练模型
KNeighborsClassifier()In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
KNeighborsClassifier()
y_pred = knn.predict(X_test_pca) # 用降维后的测试集进行测试
print(y_pred) # 将对测试集的预测结果打印出来
[ 9 21 3 40 26 4 28 37 12 36 26 7 27 21 3 24 7 2 17 24 21 32 8 2 11 19 6 29 6 29 18 10 25 35 10 18 15 5 9 22 34 29 2 16 8 18 8 38 39 35 16 30 30 11 37 36 35 20 33 6 1 16 31 18 5 30 1 39 35 39 2 19 5 8 11 4 14 27 22 24]
# 通过和之前章节类似的代码,我们可以将预测值和实际值进行对比:
import pandas as pd
a = pd.DataFrame() # 创建一个空DataFrame
a['预测值'] = list(y_pred)
a['实际值'] = list(y_test)
a.head() # 查看表格前5行
预测值 | 实际值 | |
---|---|---|
0 | 9 | 9 |
1 | 21 | 21 |
2 | 3 | 3 |
3 | 40 | 40 |
4 | 26 | 26 |
# 查看预测准确度 - 方法1
from sklearn.metrics import accuracy_score
score = accuracy_score(y_pred, y_test)
print(score)
0.9
# 查看预测准确度 - 方法2
score = knn.score(X_test_pca, y_test)
print(score)
0.9
from sklearn.neighbors import KNeighborsClassifier
knn = KNeighborsClassifier() # 建立KNN模型
knn.fit(X_train, y_train) # 不使用数据降维,直接训练
y_pred = knn.predict(X_test) # 不使用数据降维,直接测试
from sklearn.metrics import accuracy_score
score = accuracy_score(y_pred, y_test)
print(score)
0.9125
此时获得的准确度评分score为0.91,可以看到使用数据降维对提高模型预测效果还是有一些效果的,这里的数据量并不大,当数据量更大的时候,利用PCA主成分分析进行数据降维则会发挥更大的作用
12.3 人脸识别外部接口调用¶
了解了人脸识别的基本原理后,本节介绍如何调用成熟的外部接口——百度人脸识别接口,进行人脸识别及人脸打分
它的原理同样是通过特征提取和数据降维来训练人脸识别模型,不需要深究,只需要学习它的使用方法
12.3.1 baidu-aip库安装与准备工作¶
在此之前安装 baidu-aip 库
cmd
pip install baidu-aip
先在浏览器打开百度人脸识别官网, 登录账号后进入首页
点击 "立即使用", 点击左侧的 "应用列表" 然后点击 "创建应用"
勾选 "人脸识别"
应用名称和应用描述随便填
点击最下方的 "立即创建"
最后返回应用列表就可以复制创建的应用的APP_ID,API_KEY,SECRET_KEY了
12.3.2 调用接口进行人脸识别和打分¶
from aip import AipFace
import base64
# 下面3行内容为自己的APP_ID,API_KEY,SECRET_KEY
APP_ID = '118206171'
API_KEY = 'CfVdCEew4dkXALKfSTioCl9F'
SECRET_KEY = 'aDj692Tqpz6rOREP83mfSlm5wrOWIIet'
# 把上面输入的账号信息传入接口
aipFace = AipFace(APP_ID, API_KEY, SECRET_KEY)
# 下面一行内容为需要识别的人脸图片的地址,其他地方就不用改了
filePath = r'fc.png'
# 定义打开文件的函数
def get_file_content(filePath):
with open(filePath, 'rb') as fp:
content = base64.b64encode(fp.read())
return content.decode('utf-8')
img = Image.open(filePath)
# 指定要固定的高度,这里以 300 像素为例
fixed_height = 200
# 计算调整后的宽度,保持纵横比
width, height = img.size
new_width = int(width * (fixed_height / height))
# 调整图片大小
resized_img = img.resize((new_width, fixed_height), Image.LANCZOS)
imageType = "BASE64"
# 选择最后要展示的内容,这里展示age(年龄);gender(性别);beauty(颜值)
options = {}
options["face_field"] = "age,gender,beauty"
# 调用接口aipFace的detect()函数进行人脸识别,打印结果
result = aipFace.detect(get_file_content(filePath), imageType, options)
print(result)
# 打印具体信息,本质就是列表索引和字典的键值对应
age = result['result']['face_list'][0]['age']
print('年龄预测为:' + str(age))
gender = result['result']['face_list'][0]['gender']['type']
print('性别预测为:' + gender)
beauty = result['result']['face_list'][0]['beauty']
print('颜值评分为:' + str(beauty))
resized_img
{'error_code': 0, 'error_msg': 'SUCCESS', 'log_id': 1733816931, 'timestamp': 1742892393, 'cached': 0, 'result': {'face_num': 1, 'face_list': [{'face_token': 'bd58cc3ddbaa7898acdfd83422c77b83', 'location': {'left': 90.54, 'top': 313.18, 'width': 585, 'height': 634, 'rotation': 0}, 'face_probability': 1, 'angle': {'yaw': -2.55, 'pitch': -0.84, 'roll': -1.87}, 'age': 21, 'beauty': 30.28, 'gender': {'type': 'male', 'probability': 1}}]}} 年龄预测为:21 性别预测为:male 颜值评分为:30.28
这里只提取了3个参数age,gender,beauty, 更多参数可以查看官方文档